<?PHP
error_reporting(E_ALL);

if(!isset($relPath))
    $relPath = "./";

require_once $relPath."rounds.php";
require_once $relPath."words.php";
require_once $relPath."DpFile.class.php";
//require_once $relPath."DpWord.class.php";

// PageStatus values

define("PAGE_AVAIL", "page_avail");
define("PAGE_OUT",   "page_out");
define("PAGE_TEMP",  "page_temp");
define("PAGE_SAVED", "page_saved");
define("PAGE_BAD",   "page_bad");

class DpPage
{
    protected   $_row;
    protected   $_projectid;
    protected   $_pagename;
    protected   $_project;   /** @var $_project DpProject */
    protected   $_versions;
    protected   $_lines;
    protected   $_enchanted_words;

    function __construct($projectid, $pagename, &$project = null) {
        if(! $projectid) {
            die( "Project id argument omitted in DpPage." ) ;
        }
        if(! $pagename) {
            die( "Page name argument omitted in DpPage." ) ;
        }

        $this->_projectid = $projectid;
        $this->_pagename  = $pagename;
        if($project) {
            $this->_project = $project;
        }
        $this->_refresh_row($projectid, $pagename);
    }

    private function _refresh_row($projectid, $pagename) {
        global $dpdb;

        $sql = sql_project_page($projectid, $pagename);
        $this->_row = $dpdb->SqlOneRow($sql);
    }

    public function ProjectId() {
        return $this->_projectid;
    }

    public function Exists() {
        return (! empty($this->_row));
    }

    public function PageName() {
        return $this->_pagename;
    }

    public function ImageFilePath($filename = "") {
        return $filename == ""
            ? build_path($this->ProjectPath(),
                            $this->ImageFileName())
            : build_path($this->ProjectPath(), $filename);
    }

    public function IsImageFile() {
        return $this->ImageFileName() != ""
            && file_exists($this->ImageFilePath());
    }

    public function DisplayImage() {
        $imgfile = $this->ImageFilePath();
         header('Content-length: ' .filesize($imgfile));
         header('Content-type: image/jpeg');
         readfile($imgfile);
    }

    public function ProjectPath() {
        return ProjectPath($this->ProjectId());
    }

    public function UserIsPM() {
        global $User;
        return ( $User->UserName() == $this->PM() ) ;
    }

    public function PM() {
        return $this->_row['projectmanager'] ;
    }

    public function IsLanguageCode($code) {
        return in_array($code, $this->LanguageCodeArray());
    }

    public function LanguageCodeArray() {
//        global $iso_language_codes;
        $codes = $this->LanguageCodes();
        return (! $codes ) 
            ? array() 
            : preg_split("/\ +/", "codes");
    }

    public function LanguageCodes() {
        return preg_split("/ +/", $this->_row['languages']);
    }

    public function LanguageCode() {
        return $this->FirstLanguageCode();
    }

//    public function Languages() {
//        $ary = array();
//
//        foreach($this->LanguageCodes() as $code) {
//            $ary[] = LanguageName($code);
//        }
//        return implode(", ", $ary);
//    }

    public function FirstLanguageCode() {
        $codes = $this->LanguageCodes();
        return $codes[0];
    }

//    public function FirstLanguageName() {
//        $p = $this->_project;
//        return $p->FirstLanguageName();
//    }

    public function RevertToOriginal() {
        global $dpdb;
        $projectid  = $this->_projectid;
        $roundid    = $this->RoundId();
        $pagename   = $this->PageName();
        $textfld    = TextFieldForRoundId($roundid);
        $timefld    = TimeFieldForRoundId($roundid);
//        $userfld    = UserFieldForRoundId($roundid);
        $state      = $roundid.PAGE_OUT;
        $dpdb->SqlExecute("
            UPDATE project_pages
            SET $textfld = NULL,
                $timefld = NULL,
                state = '$state'
            WHERE
                projectid = '$projectid'
                AND pagename = '$pagename'");
        $this->LogPageEvent(PG_EVT_REVERT);
    }


    public function ReplaceTextPrevious($text) {
        global $dpdb;

        // strip off blank lines at end
        $idx = $this->ActiveRoundIndex();
        if($idx <= 0) {
            assert(false);
            return;
        }
        $text = preg_replace("/\t/us", "    ", $text);
        $text = preg_replace("/ +?$/m", "", $text);
        $text = preg_replace("/[\n\r\s]+\Z/m", "", $text);
        $projectid = $this->ProjectId();
        $pagename = $this->PageName();
        $textfld = TextFieldForRoundIndex($idx - 1);
//        $prevroundid = PreviousRoundIdForRoundId($this->RoundId());
        $sql = "
            UPDATE project_pages
            SET $textfld = ?
            WHERE
                projectid = '$projectid'
                AND fileid = '$pagename'";
        $args = array(&$text);
        $dpdb->SqlExecutePS($sql, $args);

        $this->LogPageEvent(PG_EVT_REPLACE_TEXT);
    }

    public function CanBeReverted() {
        return $this->PageStatus() == PAGE_SAVED
            || $this->PageStatus() == PAGE_TEMP;
    }

    public function CheckoutTime() {
        $ret = $this->_row['checkouttime'] ;
        return $ret ;
    }

    public function CompletionTime() {
        $ret = $this->_row['completiontime'] ;
        return $ret ;
    }

    public function IsAvailable() {
        return ! $this->IsCheckedOut();
    }

    public function IsCheckedOut() {
        return $this->CheckoutTime() > 0
            && $this->CompletionTime() == 0 ;
    }

    public function PageStatus() {
        if(! $this->_row['page_state']) {
            return PAGE_AVAIL;
        }
        $ary = preg_split("/\./", $this->_row['page_state']);
        return $ary[1];
    }

    public function RoundState() {
        return $this->PageRoundState();
    }

    public function PageRoundState() {
        return sprintf("%s.%s", 
                $this->RoundId(), $this->PageStatus());
        // return $this->_row['page_state'];
    }

    public function ProjectRoundState() {
        return $this->_row['project_state'];
    }

    protected function project() {
        if(!$this->_project) {
            $this->_project = new DpProject($this->ProjectId());
        }
        return $this->_project;
    }

/*
    public function SpellWordLines($langcode) {
        return spell_wordsets($langcode, $this->Text());
    }
*/

    public function EnchantedWords($langcode) {
        if(! isset($this->_enchanted_words[$langcode])) {
            $this->_enchanted_words[$langcode] =
                new DpEnchantedWords($langcode, 
                        $this->ActiveText(), $this->ProjectId());
        }
        return $this->_enchanted_words[$langcode];
    }

    // accept a new text version from UI, save temp, and check it.
    public function WordCheckText($langcode, $text) {
        // spellcheck the submitted text
        $this->SaveTemp($text);
        // break page text into lines

        $ptn = array("/</", "/>/");
        $rpl = array("&lt;", "&gt");
        $text = preg_replace($ptn, $rpl, $text);

        $tlines = text_lines($text);
        // $tlines = $this->TextLines();
        // create array for all words on page
        // which will be in format:
        //   $a[word][$line][i] = line-offset
        // so there can be multiple copies of a word on a line
//        $twords = array();
        // get array of words, one element per instance
        // array(array("foo", 0), array("bar", 4), ... )

        // collect lists
        $fwa = $this->FlagWordsArray($langcode);
        $swa = $this->SuggestedWordsArray($langcode);
        $bwa = $this->BadWordsArray($langcode);

        // for each line
        $nwc = $nwcs = $nwcb = 0;
        for($i = 0; $i < count($tlines); $i++) {
            $tline = $tlines[$i];
            //
            // added this line to handle embedded markup
            //
            // $tline = h($tline);
            //
            // added this line to handle embedded markup
            //
            $rwo = RegexStringsOffsets($tline, $langcode);
            if(count($rwo) == 0) {
                continue;
            }
            for($j = count($rwo)-1; $j >= 0; $j--) {
                list($wd, $locn) = $rwo[$j];
                if(! in_array($wd, $fwa)) {
                    continue;
                }
                if(in_array($wd, array("sc", "hr", "b", "f", "g"))) {
                    continue;
                }
                if(in_array($wd, $swa)) {
                    $nwcs++;
                    $class = "wcs";
                }
                else if(in_array($wd, $bwa)) {
                    $nwcb++;
                    $class = "wcb";
                }
                else {
                    $nwc++;
                    $class = "wc";
                }
                $tline = bleft($tline, $locn)
                    ."<span id='wc_{$i}_{$j}'"
                    ." class='{$class}'>" .$wd."</span>"
                    .bmid($tline, $locn + strlen($wd));
            }
            $tlines[$i] = $tline;
        }
        return array($nwc, $nwcs, $nwcb, implode("\n", $tlines));
    }

    public function AcceptWordsArray($langcode, $warray) {
        $this->SuggestWordsArray($langcode, $warray);
    }

    public function SuggestWordsArray($langcode, $w_array) {
        /** @var $proj DpProject */
        $proj = $this->project();
        $proj->SuggestWordsArray($langcode, $w_array);
    }

    public function SuggestedWordsArray($langcode) {
        /** @var $proj DpProject */
        $proj = $this->project();
        return $proj->SuggestedWordsArray($langcode);
    }

    public function GoodWordsArray($langcode) {
        /** @var $proj DpProject */
        $proj = $this->project();
        return $proj->GoodWordsArray($langcode);
    }

    public function SpellWordCountTotal($langcode) {
        return count($this->SpellWordLineArray($langcode));
    }

    public function FlagWordsArray($langcode) {
        /** @var $p DpProject */
        $p = $this->project();
        return $p->FlagWordsArray($langcode);
        // $ary = array_merge($this->SpellWordsArray($langcode),
                        // $this->BadWordsArray($langcode));
        // $ary = array_diff($ary, $this->GoodWordsArray($langcode));
        // return $ary;
    }

    public function BadWordsArray($langcode) {
        /** @var $proj DpProject */
        $proj = $this->project();
        return $proj->BadWordsArray($langcode);
    }

    public function SpellWordsArray($langcode) {
        /** @var $ew DpEnchantedWords */
        $ew = $this->EnchantedWords($langcode);
        return $ew->WordsArray();
    }

//    public function RawEnchantedWords($langcode) {
//        /** @var $ew DpEnchantedWords */
//        $ew = $this->EnchantedWords($langcode);
//        return $ew->RawOutput();
//    }
    
    public function SpellWordLineArray($langcode) {
        /** @var $ew DpEnchantedWords */
        $ew = $this->EnchantedWords($langcode);
        return $ew->WordLineArray();
    }
    
//    public function SubmitGoodWordsArray($langcode, $w_array) {
//        /** @var $proj DpProject */
//        $proj = $this->project();
//        return $proj->SubmitGoodWordsArray($langcode, $w_array);
//    }
//
//    public function SubmitBadWordsArray($langcode, $w_array) {
//        /** @var $proj DpProject */
//        $proj = $this->project();
//        return $proj->SubmitBadWordsArray($langcode, $w_array);
//    }


    public function ProjectState() {
        return $this->_row['project_state'];
    }

    public function RoundId() {
        if($this->_project) {
            $p = $this->Project();
            /** @var $p DpProject */
            return $p->RoundId();
        }
        $ary = preg_split("/\./", $this->_row['project_state']);
        return $ary[0];
    }

    public function RoundIndexText($index) {
        if($index == 0) {
            $colname = "master_text";
        }
        else {
            $colname = sprintf("round%d_text", $index);
        }
        return $this->_row[$colname];
    }

    public function PrevText() {
        $idx = RoundIndexForId($this->RoundId());
        if($idx <= 0)
            return "";
        return $this->RoundText(RoundIdForIndex($idx-1));
    }

    public function RoundText($roundid) {
        $fld = TextFieldForRoundId($roundid);
        return $this->_row[$fld];
    }

    public function LastUser() {
        return $this->_row['last_user'];
    }

    public function LastText() {
        return $this->_row['last_text'];
    }

    public function ProofersArray() {
        $ary = array();
        foreach(RoundIdsInOrder() as $rid) {
            $usr = $this->RoundUser($rid);
            if($usr != "") {
                $ary[] = $usr;
            }
        }
        return $ary;
    }

    
    public function Text() {
        return $this->ActiveText();
    }

    public function ActiveText() {
        $roundid = $this->RoundId();
        $prevroundid = RoundIdBefore($roundid);
//        $rtntext = "";

        switch($this->PageStatus()) {
            case PAGE_TEMP:
            case PAGE_SAVED:
                $rtntext = $this->RoundText($roundid);
                break;
            case PAGE_AVAIL:
            case PAGE_OUT:
            case PAGE_BAD:
                $rtntext = $this->RoundText($prevroundid);
                break;
            default:
                die("Status error: ".$this->PageStatus()
                ." in ActiveText() page ".$this->PageName());
        }
        return $rtntext;
    }

    public function ActiveHtmlText() {
        return h($this->ActiveText());
    }

    public function NextRoundText() {
        return $this->RoundText($this->ActiveRoundIndex() + 1);
    }

    public function ActiveUser() {
        global $User;
        $roundid = $this->RoundId();
        if($roundid == "OCR") {
            return $User->Username();
        }
        $field = UserFieldForRoundId($roundid);
        return $this->_row[$field];
    }

    public function RoundUser($roundid) {
        $userfield = UserFieldForRoundId($roundid);
        return $userfield == "" ? "" : $this->_row[$userfield];
    }

    public function Owner() {
        switch($this->PageStatus()) {
            case PAGE_SAVED:
            case PAGE_TEMP:
                return $this->RoundUser($this->RoundId());
            default:
                return null;
        }
    }

    private function ActiveRoundIndex() {
        return RoundIndexForId($this->RoundId());
    }

    public function TextLines() {
        if(! $this->_lines) {
            $this->_lines = text_lines($this->ActiveText());
        }
        return $this->_lines;
    }

    public function LineCount() {
        return count($this->TextLines());
    }

    public function Tweets() {
        $a = preg_split("/~/", $this->_row['tweets']);
        return $a;
    }

    public function SaveTweets($atweets) {
        global $dpdb;
        if(! is_array($atweets)) {
            $atweets = array($atweets);
        }
        $tweets = implode(chr(0), $atweets);
        $sql = "UPDATE project_pages
                SET tweets = ?
                WHERE projectid = ?
                    AND fileid = ?";
        $projectid = $this->ProjectId();
        $pagename  = $this->PageName();
        $args = array(&$projectid, &$pagename, &$tweets);
        /** @var $ret DpDb */
        $ret = $dpdb->SqlExecutePS($sql, $args);
    }

    public function LastTweet() {
        $a = $this->Tweets();
        return $a[count($a)];
    }

//    public function Versions() {
//        if(! isset($this->_versions)) {
//            $rows = $dpdb->SqlRows("
//                SELECT sequence,
//                       roundid,
//                       auto_advance
//                FROM project_round_sequence AS prs
//                JOIN projects AS p
//                    ON prs.sequence_code = p.sequence_code
//                WHERE p.projectid = '{$this->ProjectId()}'");
//
//            $this->_versions = array();
//            foreach($rows as $row) {
//                $fldpfx = "Round".$row['sequence'];
//                $textfld = $fldpfx."_text";
//                $userfld = $fldpfx."_user";
//                $timefld = $fldpfx."_time";
//                $this->_versions[] = new DpPageVersion(
//                                        $this->_row[$textfld],
//                                        $this->_row[$userfld],
//                                        $row['roundid'],
//                                        $this->_row[$timefld]);
//            }
//        }
//        return $this->_versions;
//    }

//    public function ActiveVersion() {
//        static $_vsn ;
//        require_once "PageVersion.class.php" ;
//        if( ! isset( $_vsn ) ) {
//           $_vsn = new PageVersion( $this, $this->MaxVersionNumber() ) ;
//        }
//        return $_vsn ;
//    }

    public function NameOfWork() {
        return $this->_row['nameofwork'] ;
    }

    public function AuthorsName() {
        return $this->_row['authorsname'] ;
    }

    public function ActiveTaskCode() {
        return $this->_row['taskcode'] ;
    }

    public function CharTableUrl() {
        return $this->_row['chartableurl'] ;
    }

//    public function Version( $vnum ) {
//        $projectid = $this->ProjectId() ;
//        $pageName = $this->PageName() ;
//        require_once "PageVersion.class.php" ;
//        return new PageVersion( $this, $vnum ) ;
//    }
//
//    public function InitTextFromPath($path) {
//        $v = $this->Version( 0 ) ;
//        $v->SetFromPath( $path );
//    }

    public function InitImageFromPath($path) {
        $topath = $this->ImageFilePath();
        copy($path, $topath);
    }

    public function ImageFileName() {
        return $this->_row['imagefilename']; 
    }

    public function ImageFileSize() {
        return 
        $this->IsImageFile()
            ? filesize($this->ImageFilePath())
            : 0 ;
    }

//    public function TextPath() {
//        return build_path($this->PagePath(), "v0.txt");
//    }

    public function ImageUrl() {
        return url_for_page_image(
            $this->ProjectId(), $this->PageName());
    }

    public function PrevImageUrl() {
        global $dpdb;
        $sql = "SELECT MAX(fileid) FROM pages
                WHERE fileid < '{$this->PageName()}'";
        $pagename = $dpdb->SqlOneValue($sql);
        return url_for_page_image($this->ProjectId(), $pagename);
    }
    public function NextImageUrl() {
        global $dpdb;
        $sql = "SELECT MIN(fileid) FROM pages
                WHERE fileid > '{$this->PageName()}'";
        $pagename = $dpdb->SqlOneValue($sql);
        return url_for_page_image($this->ProjectId(), $pagename);
    }

//    public function MakeThumbnail() {
//        //'find' the source image
//        assert(file_exists($this->ImageFilePath()));
//        $src_img = ImageCreateFrompng($this->ImageFilePath());
//
//        //Get original image width and height
//        $src_width = imagesx($src_img);
//        $src_height = imagesy($src_img);
//
//        //Our target output width
//        //(image will scale down completely to this width)
//        $dest_width = 125;
//
//        //Calculate our output height proportionally
//        $dest_height = $src_height * $dest_width / $src_width;
//
//        // create a shell image
//        $dest_img = imagecreate($dest_width, $dest_height);
//
//        imagecopyresampled($dest_img, $src_img,
//                            0, 0, 0 ,0,
//                            $dest_width, $dest_height,
//                            $src_width, $src_height);
//
//        //write it out
//        imagepng($dest_img, $this->ThumbFilePath());
//
//        //clean up memory
//        imagedestroy($src_img);
//        imagedestroy($dest_img);
//    }
//
//    public function ThumbExists() {
//        $path = $this->ThumbFilePath();
//        return file_exists($path);
//    }

//    public function IsThumb() {
//        return substr($this->_path, 0, 5) === "thumb";
//    }
//
//    public function ThumbFileName() {
//        return "thumb.".$this->PageName().".png";
//    }
//
//    // url to thumbnail, optional to create if necessary
//    public function ThumbFilePath($ismake = false) {
//        $path = build_path($this->PagePath(), $this->ThumbFileName());
//        if((! file_exists($path)) && $ismake)
//            $this->MakeThumbnail();
//        return $path;
//    }
//
//    public function ThumbUrl($ismake = false) {
//        global $projects_url;
//        if(! $this->ThumbExists() && $ismake)
//            $this->MakeThumbnail();
//
//        return $projects_url
//            ."/".$this->ProjectId()
//            ."/".$this->PageName()
//            ."/".$this->ThumbFileName();
//    }

//    public function ImageLink($prompt) {
//        return '<a href="{this->ImageUrl()}"$prompt</a>\n';
//    }

//    public function ProjectUrl() {
//        return "myproject.php?projectid={$this->_projectid}";
//    }
//
//    public function VersionCount() {
//        return $this->_row['versioncount'];
//    }
//
//    public function MaxVersionNumber() {
//        static $id ;
//
//        if( ! isset( $id ) )
//        {
//            global $db ;
//            $projectid = $this->_projectid ;
//            $pagename  = $this->PageName();
//            $sql = "SELECT IFNULL( MAX( versionnumber ), 0 )
//                    FROM pageversions AS pv
//                    JOIN pagetasks AS pt
//                        ON pv.pagetaskid = pt.pagetaskid
//                    WHERE pt.projectid = '$projectid'
//                        AND pt.pageid = '$pagename'";
//            $id = $db->SqlOneValue( $sql ) ;
//        }
//        return $id ;
//    }

    public function SourcePageName() {
        return $this->_row['sourcepagename'];
    }

    public function ProjectComments() {
        return $this->_row['comments'];
    }
    
    public function ProjectManager() {
        return $this->_row['projectmanager'] ;
    }

    public function Title() {
        return $this->_row['title'];
    }

    public function Author() {
        return $this->_row['author'];
    }

    public function MasterText() {
        return $this->_row['master_text'];
    }

    public function Delete() {
        global $dpdb;

        $projectid = $this->ProjectId();
        $pagename = $this->PageName();
        
        $dpdb->SqlExecute( "
                UPDATE project_pages
                SET state = 'deleted',
                    is_active = 0
                WHERE projectid = '$projectid'
                    AND state != 'deleted'
                    AND fileid = '$pagename'");
        $this->LogPageEvent(PG_EVT_DELETE);
        // test the following for proper recursion
    }
    
    public function CheckOutPage() {
        global $User;
        global $dpdb;
        $projectid  = $this->ProjectId();
        $pagename   = $this->PageName();
        $username   = $User->UserName();
        $roundid    = $this->RoundId();
        $userfield  = UserFieldForRoundId($roundid);
        $timefield  = TimeFieldForRoundId($roundid);
//        $state      = $this->PageRoundState();
        $newstate   = $roundid.".".PAGE_OUT;
        
        $sql = "
                    UPDATE pages
                    SET $userfield = '$username',
                        $timefield = UNIX_TIMESTAMP(),
                        state = '$newstate'
                    WHERE projectid = '$projectid'
                        AND fileid = '$pagename'";
        $nrecs = $dpdb->SqlExecute( $sql );
        if($nrecs > 0) {
            $this->LogPageEvent(PG_EVT_CHECK_OUT);
        }
        return ($nrecs > 0)
            ? $this->ActiveText()
            : null;
    }

    public function ResumePage() {
        global $User;
        global $dpdb;
        $projectid = $this->ProjectId();
        $pagename = $this->PageName();
        $username = $User->UserName();
        $roundid = $this->RoundId();
        $userfield = UserFieldForRoundId($roundid);
        $timefield = TimeFieldForRoundId($roundid);
//        $state = $this->PageRoundState();
        $newstate = 
            $this->PageRoundState() == $roundid.".".PAGE_TEMP
                ? $this->PageRoundState() 
                : $roundid.".".PAGE_OUT;
        $nrecs = $dpdb->SqlExecute("
                UPDATE pages
                SET $userfield = '$username',
                    $timefield = UNIX_TIMESTAMP(),
                    state = '$newstate'
                WHERE projectid = '$projectid'
                    AND fileid = '$pagename'");

        if($nrecs > 0) {
            $this->LogPageEvent(PG_EVT_RESUME);
        }
        return ($nrecs > 0)
            ? $this->ActiveText()
            : null;
    }

    public function SaveTemp($text) {
        $this->SaveAsInProgress($text);
        $this->_refresh_row($this->ProjectId(), $this->PageName());
    }

    public function SaveAsInProgress($text) {
        global $User;
        global $dpdb;

        $projectid = $this->ProjectId();
        $pagename = $this->PageName();
        $username = $User->UserName();
        $roundid = $this->RoundId();
        $userfield = UserFieldForRoundId($roundid);
        $timefield = TimeFieldForRoundId($roundid);
        $textfield = TextFieldForRoundId($roundid);
//        $state = $this->PageRoundState();
        $newstate = $roundid . ".".PAGE_TEMP;

        $text = preg_replace("/\t/us", "    ", $text);
        $text = preg_replace("/ +?$/sm", "", $text);
        // keep trailing whitespace - may be appending in progress
        // $text = preg_replace("/[\n\r\s]+\Z/m", "", $text);

        $sql = "UPDATE project_pages
                SET $userfield = ?,
                    $timefield = UNIX_TIMESTAMP(),
                    state = ?,
                    $textfield = ?
                WHERE projectid = '$projectid'
                    AND fileid = '$pagename'";
        $args = array(&$username, &$newstate, &$text);
        $ret = $dpdb->SqlExecutePS($sql, $args);

        $this->LogPageEvent(PG_EVT_SAVE_TEMP);
        return $ret;
    }

    public function SaveAsDone($text) {
        global $User;
        global $dpdb;

        $projectid = $this->ProjectId();
        $pagename = $this->PageName();
        $username = $User->UserName();
        assert($username != "");
        $roundid = $this->RoundId();
        $userfield = UserFieldForRoundId($roundid);
        $timefield = TimeFieldForRoundId($roundid);
        $textfield = TextFieldForRoundId($roundid);
//        $state = $this->PageRoundState();
        $newstate = $roundid . ".".PAGE_SAVED;

        $text = preg_replace("/\t/us", "    ", $text);
        $text = preg_replace("/ +?$/m", "", $text);
        $text = preg_replace("/[\n\r\s]+\Z/m", "", $text);

        $sql = "UPDATE project_pages
                SET $userfield = ?,
                    $timefield = UNIX_TIMESTAMP(),
                    state = ?,
                    $textfield = ?
                WHERE projectid = '$projectid'
                    AND fileid = '$pagename'";
        $args = array(&$username, &$newstate, &$text);
        $ret = $dpdb->SqlExecutePS($sql, $args);
        $this->LogPageEvent(PG_EVT_SAVE_DONE);
        /** @var $proj DpProject */
        $proj = $this->Project();
        $proj->MaybeSendTaskCompleteEmail();
        return $ret;
    }

    public function ReOpen() {
        global $User;
        global $dpdb;
        $projectid = $this->ProjectId();
        $pagename = $this->PageName();
        $username = $User->UserName();
        $roundid = $this->RoundId();
        $userfield = UserFieldForRoundId($roundid);
        $timefield = TimeFieldForRoundId($roundid);
//        $textfield = TextFieldForRoundId($roundid);
//        $state = $this->PageRoundState();
        $newstate = $roundid .".".PAGE_OUT;
        $nrecs = $dpdb->SqlExecute( "
                UPDATE project_pages
                SET $timefield = UNIX_TIMESTAMP(),
                    state = '$newstate'
                WHERE projectid = '$projectid'
                    AND fileid = '$pagename'
                    AND $userfield = '$username'");

        if($nrecs > 0) {
            $this->LogPageEvent(PG_EVT_REOPEN);
        }
        return ($nrecs > 0)
            ? $this->ActiveText()
            : null;
    }

    public function ReturnToRound() {
        global $User;
        global $dpdb;
        $projectid  = $this->ProjectId();
        $pagename   = $this->PageName();
        $roundid    = $this->RoundId();
        $username   = $User->Username();
//        $state      = $this->PageRoundState();
        $userfield  = UserFieldForRoundId($roundid);
        $timefield  = TimeFieldForRoundId($roundid);
        $textfield  = TextFieldForRoundId($roundid);
        $newstate   = $roundid .".".PAGE_AVAIL;

        $dpdb->SqlExecute("
                UPDATE project_pages
                SET $userfield = NULL,
                    $timefield = NULL,
                    $textfield = NULL,
                    state = '$newstate'
                WHERE projectid = '$projectid'
                    AND fileid = '$pagename'
                    AND $userfield = '$username'");

        $this->LogPageEvent(PG_EVT_RETURN);
    }

    // May the current user select this page for editing?
    // Yes, if they currently have it checked out,
    // or they may manage the project.
    public function MayBeSelectedByActiveUser() {
        if($this->IsBad() && ! $this->UserMayManage())
            return false;
        // $p = $this->project(); 
        return $this->ActiveUserIsEditor()
            || $this->UserMayManage();
    }

    public function UserMayManage() {
        /** @var $p DpProject */
        $p = $this->project(); 
        return $p->UserMayManage();
    }

    public function MayBeMarkedBadByActiveUser() {
        return true;
    }

    public function ActiveUserIsEditor() {
        global $User;
        foreach(RoundIdsInOrder() as $rid) {
            // $fld = UserFieldForRoundId($rid);
            if($this->RoundUser($rid) == $User->Username()) {
                return true;
            }
        }
        return false;
    }

    public function BadReporter() {
        return empty($this->_row['b_user'])
            ? null
            : new DpUser($this->_row['b_user']);
    }

    public function BadReporterUserName() {
        return $this->_row['b_user'];
    }

    public function BadReason() {
        return $this->_row['b_comment'];
    }

    public function IsBad() {
        return $this->PageStatus() == PAGE_BAD;
    }

    public function MarkBad($comment) {
        global $User;
        global $dpdb;

        $projectid = $this->ProjectId();
        $pagename  = $this->PageName();
        $username  = $User->UserName();
//        $state     = $this->PageRoundState();
        $newstate  = $this->RoundId().".".PAGE_BAD;

        $dpdb->SqlExecute( "
                UPDATE project_pages
                SET b_user = '$username',
                    b_comment = '$comment',
                    state = '$newstate'
                WHERE projectid = '$projectid'
                    AND fileid = '$pagename'");

        $this->LogPageEvent( PG_EVT_SET_BAD );
        $this->SendBadPageEmail($username, $comment);
    }

    private function SendBadPageEmail($username, $comment) {
        global $Context;
//        global $User;
        $Context->SendUserEmail($this->PM(),
            "DP-It "
            ._("notification {$this->Title()} - page marked bad"),
            _("
To the project manager for ".$this->Title().",

User ".$username." has marked page " .$this->PageName()
." as a 'bad page' in this project.

Reason: $comment

Thanks!

The Administration"));
    }

    public function ClearBad() {
        global $dpdb;
        $projectid = $this->ProjectId();
        $pagename = $this->PageName();
        $roundid =  $this->RoundId();
        $pagestate = "{$roundid}.".PAGE_AVAIL;
        $dpdb->SqlExecute( "
                UPDATE project_pages
                SET b_user = NULL,
                    b_code     = NULL,
                    b_comment  = NULL,
                    state      = '$pagestate' 
                WHERE projectid = '$projectid'
                    AND fileid = '$pagename'");
        $this->LogPageEvent( PG_EVT_CLEAR_BAD );
    }

    public function Clear($roundid) {
        global $dpdb;

        $userfield = UserFieldForRoundId($roundid);
        $timefield = TimeFieldForRoundId($roundid);
        $textfield = TextFieldForRoundId($roundid);
        $state = PAGE_AVAIL;
        $sql = "
            UPDATE pages
            SET state = '{$roundid}.{$state}',
                $userfield = NULL,
                $timefield = NULL,
                $textfield = NULL
            WHERE
                projectid  = '{$this->ProjectId()}'
                AND fileid = '{$this->PageName()}'";
        $dpdb->SqlExecute($sql);
        $this->LogPageEvent( PG_EVT_REVERT );
    }

    protected function LogPageEvent($event_type, $note = "") {
        global $Context;
        $Context->LogPageEvent($this, $event_type, $note);
    }

    protected function LogPageLoadEvent($event_type, $note = "") {
        global $dpdb;

        $dpdb->SqlExecute("
            INSERT INTO page_events
            SET event_time = UNIX_TIMESTAMP(),
                projectid  = '{$this->ProjectId()}',
                page_name  = '{$this->PageName()}',
                username   = 'extern_load',
                task_code  = '{$this->RoundId()}',
                event_type = '$event_type',
                note       = ".SqlQuote($note));
    }

    private function LogPageError($errmsg, $comment) {
        global $dpdb;
        $dpdb->SqlExecute("
            INSERT INTO page_errors
            SET eventtime = UNIX_TIMESTAMP(),
                errormessage = '$errmsg',
                comment = '$comment'");
    }
}

class DpPageVersion
{
    private $_text;
    private $_proofer;
    private $_taskcode;
    private $_versiontime;

    public function __construct($text, $proofer, $taskcode, $vtime) {
        $this->_text = $text;
        $this->_proofer = $proofer;
        $this->_taskcode = $taskcode;
        $this->_versiontime = $vtime;
    }

    public function Text() {
        return $this->_text;
    }

    public function Proofer() {
        return $this->_proofer;
    }
    
    public function TaskCode() {
        return $this->_taskcode;
    }

    public function VersionTime() {
        return $this->_versiontime;
    }
}

// A new page to be added from scratch

class DpProtoPage extends DpPage
{
    private $_extimgfile;
    private $_exttextfile;

    public function IsExternalImageFile() {
        /** @var $f DpFile */
        $f = $this->ExternalImageFile();
        return $f ? $f->Exists() : false;
    }

    public function SetExternalImageFile($f) {
        $this->_extimgfile = $f;
    }

    public function SetExternalTextFile($f) {
        $this->_exttextfile = $f;
    }

    public function DisposeExternalFiles() {
        $this->DisposeExternalImageFile();
        $this->DisposeExternalTextFile();
    }

    public function DisposeExternalTextFile() {
        /** @var $f DpFile */
        $f = $this->ExternalTextFile();
        if($f) $f->Delete();
    }

    public function DisposeExternalImageFile() {
        /** @var $f DpFile */
        $f = $this->ExternalImageFile();
        if($f) $f->Delete();
    }

    public function ExternalTextFile() {
        if(isset($this->_exttextfile)) {
            return $this->_exttextfile;
        }
        $path = build_path($this->UploadPath(), 
                            $this->PageName().".txt"); 

        if(file_exists($path)) {
            $this->SetExternalTextFile(new DpFile($path));
            return $this->_exttextfile;
        }
        return null;
    }

    public function ExternalImageFile() {
        if(isset($this->_extimgfile)) {
            return $this->_extimgfile;
        }

        $path = build_path($this->UploadPath(), 
                            $this->PageName().".png"); 
        if(file_exists($path)) {
            $this->SetExternalImageFile(new DpFile($path));
            return $this->_extimgfile;
        }

        $path = build_path($this->UploadPath(), 
                                $this->PageName().".jpg"); 
        if(file_exists($path)) {
            $this->SetExternalImageFile(new DpFile($path));
            return $this->_extimgfile;
        }

        $path = build_path($this->UploadPath(), 
                                $this->PageName().".gif"); 
        if(file_exists($path)) {
            $this->SetExternalImageFile(new DpFile($path));
            return $this->_extimgfile;
        }

        return null;
    }

    public function ExternalImageFilePath() {
        /** @var $f DpFile */
        $f = $this->ExternalImageFile();
        return $f ? $f->FilePath() : "";
    }

    public function ExternalImageFileName() {
        /** @var $f DpFile */
        $f = $this->ExternalImageFile();
        return $f ? $f->FileName() : "";
    }

    private function UploadPath() {
        return ProjectUploadPath($this->ProjectId());
    }

/*
    public function ExternalImageUrl() {
        $f = $this->ExternalImageFile();
        return $f ? $f->Url() : "";
    }
*/

    public function ExternalTextFileName() {
        /** @var $f DpFile */
        $f = $this->ExternalTextFile();
        return $f ? $f->FileName() : "";
    }

    public function ExternalTextFilePath() {
        /** @var $f DpFile */
        $f = $this->ExternalTextFile();
        return $f ? $f->FilePath() : "";
    }

    public function UploadedTextFilePath() {
        return $this->ExternalTextFilePath();
    }

    public function IsExternalText() {
        /** @var $f DpFile */
        $f = $this->ExternalTextFile();
        return $f ? $f->Exists() : false;
    }

    private function IsExternalTextFile() {
        return $this->IsExternalText();
    }

    public function ExternalImageFileSize() {
        /** @var $f DpFile */
        $f = $this->ExternalImageFile();
        return $f ? $f->Size() : "";
    }

    public function ExternalText() {
        /** @var $f DpFile */
        $f = $this->ExternalTextFile();
        return $f ? $f->Text() : "";
    }

    private function ReplaceText() {
        global $dpdb;
//        global $User;
        $projectid = $this->ProjectId();
        $pagename  = $this->PageName();
        $text      = $this->ExternalText();
        if(! is_utf8($text)) {
            $text = to_utf8($text);
        }

        if($this->RoundId() == "OCR") {
            $sql = "UPDATE project_pages
                    SET master_text = ?
                    WHERE projectid = ?
                        AND fileid = ?";
            $args = array(&$text, &$projectid, &$pagename);
        }
        else {
            $timefield = TimeFieldForRoundId($this->RoundId());
            $textfield = TextFieldForRoundId($this->RoundId());
            $userfield = UserFieldForRoundId($this->RoundId());
            $state = $this->RoundId().".".PAGE_SAVED;
            $username  = "extern_load";

            $sql = "UPDATE project_pages
                    SET {$textfield} = ?,
                        {$timefield} = UNIX_TIMESTAMP(),
                        {$userfield} = ?,
                        state = ?
                    WHERE projectid = ?
                        AND fileid = ?";
            $args = array(&$text, &$username, &$state,
                            &$projectid, &$pagename);
            // dump($sql);
            // dump($args);
            // die();
        }
        
        $dpdb->SqlExecutePS($sql, $args);
        $this->LogPageLoadEvent(PG_EVT_REPLACE_TEXT);
    }

    public function AddOrReplace() {
//        global $dpdb;

        if($this->Exists()) {
            if($this->IsExternalImageFile()) {
                $this->ReplaceImageFile();
            }

            if($this->IsExternalText()) {
                $this->ReplaceText();
            }
        }
        else {
            $this->AddPage();
        }
    }

    public function ReplaceImageFile() {
        global $dpdb;
        if(! $this->IsExternalImageFile()) {
            return;
        }
        $imagebase = $this->ExternalImageFileName();
        $frompath  = $this->ExternalImageFilePath();
        $topath    = $this->ImageFilePath($imagebase);
        copy($frompath, $topath);

        $projectid = $this->ProjectId();
        $pagename  = $this->PageName();

        $sql = "UPDATE project_pages
                SET image = ?
                WHERE projectid = ?
                    AND fileid = ?";

        $args = array(&$imagebase, &$projectid, &$pagename);
        // dump($sql);
        // dump($args);

        $dpdb->SqlExecutePS($sql, $args);
        $this->LogPageEvent(PG_EVT_REPLACE_IMAGE);
    }

    private function AddPage() {
        global $dpdb;
        $username   = "extern_load";
        $projectid  = $this->ProjectId();
        $pagename   = $this->PageName();
        $pagestate  = $this->RoundId().".".PAGE_SAVED;
        if($this->IsExternalImageFile()) {
            $imagepath = $this->ExternalImageFilePath();
            $imagebase = basename($imagepath);
            if($imagebase != "") {
                $toimagepath = 
                   build_path($this->ProjectPath(), $imagebase);
                if(file_exists($toimagepath)) {
                    unlink($toimagepath);
                }
                // dump("copy $imagepath to $toimagepath");
                copy($imagepath, $toimagepath);
                unlink($imagepath);
            }
        }
         $sql = "INSERT INTO project_pages
                 SET projectid = ?,
                     fileid = ?,
                     state = ?";
        $args = array(&$projectid, &$pagename, &$pagestate);

        if(isset($imagebase) && $imagebase != "") {
            $sql .= ",
                    image = ?";
            $args[] = &$imagebase;
        }

        if($this->IsExternalTextFile()) {
            $text = $this->ExternalText();
            $textfield = TextFieldForRoundId($this->RoundId());
            $sql .= ",
                    {$textfield} = ?";
            $args[] = &$text;

            if($this->RoundId() != "OCR") {
                $userfield = UserFieldForRoundId($this->RoundId());
                $timefield = TimeFieldForRoundId($this->RoundId());
                $sql .= ",
                    {$userfield} = ?,
                    {$timefield} = UNIX_TIMESTAMP()";
                $args[] = &$username;
            }

        }

//           dump($args);
//           dump($sql);
//            die();
        $ret = $dpdb->SqlExecutePS($sql, $args);
        assert($ret == 1);
    }
}
// end DpProtoPage

function sql_project_page($projectid, $pagename) {
    return "
        SELECT
            pp.projectid,
            pp.fileid as pagename,
            pp.image as imagefilename,
            pp.state AS page_state,

            round1_user,
            round2_user,
            round3_user,
            round4_user,
            round5_user,
            round6_user,
            round7_user,
            round8_user,
            round9_user,
            round10_user,
            COALESCE(
                round10_user,
                round9_user, round8_user,
                round7_user, round6_user,
                round5_user, round4_user,
                round3_user, round2_user,
                round1_user, ''
            ) AS last_user,

            round1_time,
            round2_time,
            round3_time,
            round4_time,
            round5_time,
            round6_time,
            round7_time,
            round8_time,
            round9_time,
            round10_time,
            COALESCE(
                round10_time,
                round9_time, round8_time,
                round7_time, round6_time,
                round5_time, round4_time,
                round3_time, round2_time,
                round1_time, ''
            ) AS last_time,

            master_text,
            round1_text,
            round2_text,
            round3_text,
            round4_text,
            round5_text,
            round6_text,
            round7_text,
            round8_text,
            round9_text,
            round10_text,
            COALESCE(
                round10_text,
                round9_text, round8_text,
                round7_text, round6_text,
                round5_text, round4_text,
                round3_text, round2_text,
                round1_text, master_text
            ) AS last_text,

            b_user,
            b_comment,

            p.language,
            TRIM(CONCAT(p.language, ' ', 
                IFNULL(p.other_languages, ''))) AS languages,
            p.comments,
            p.username AS projectmanager,
            p.nameofwork AS title,
            p.authorsname AS author,
            p.state AS project_state,
            pp.tweets

        FROM pages AS pp
        JOIN projects AS p
            ON '$projectid' = p.projectid
        WHERE pp.projectid = '$projectid'
            AND pp.fileid = '$pagename' ";
}

?>
